package com.examples.chartdirector.FinancialCharts;
import java.awt.BorderLayout;
import java.awt.Checkbox;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import javax.swing.Box;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;

import com.examples.chartdirector.FinancialCharts.model.ISelectedItem;
import com.examples.chartdirector.ui.DefaultComboBoxItemRenderer;

import ChartDirector.ArrayMath;
import ChartDirector.BaseChart;
import ChartDirector.Chart;
import ChartDirector.ChartViewer;
import ChartDirector.FinanceChart;
import ChartDirector.FinanceSimulator;
import ChartDirector.LineLayer;
import ChartDirector.MultiChart;
import ChartDirector.TrackCursorAdapter;
import ChartDirector.ViewPortAdapter;
import ChartDirector.ViewPortChangedEvent;
import ChartDirector.XYChart;


@SuppressWarnings("serial")
public class FinanceDemoChart extends JDialog
{
	private ChartViewer chartViewer1;
	// The timeStamps, volume, high, low, open and close data
	private Date[] timeStamps = null;
	double[] volData = null;
	double[] highData = null;
	double[] lowData = null;
	double[] openData = null;
	double[] closeData = null;

	// An extra data series to compare with the close data
	double[] compareData = null;
	// The resolution of the data in seconds. 1 day = 86400 seconds.
	int resolution = 86400;

	static class Bounds extends Rectangle {
		public Bounds() {
			super(10, 0, 118, 24);
		}

		public Bounds(Component c) {
			super(10, c.getBounds().y + ((c instanceof JLabel || c instanceof Checkbox) ? 22 : 28), 160, 24);
		}
	}

	private JComboBox<Integer> movAvg1, movAvg2;
	private JComboBox<String> mChartSize, mChartType, mBand;
	private JComboBox<ISelectedItem> mTimePeriod, Indicator1, Indicator2, Indicator3, Indicator4, avgType1, avgType2;
	private JCheckBox mVolume, mParabolicSAR, mLogScale, mPercentageScale;
	private JTextField mTickerSymbol;

	private JComboBox<ISelectedItem> createComboBox(final ISelectedItem items[]) {
		JComboBox<ISelectedItem> comboBox = new JComboBox<>();
		comboBox.addActionListener(this::itemStateChanged);
		comboBox.setModel(new DefaultComboBoxModel<ISelectedItem>(items));
		comboBox.setRenderer(new DefaultComboBoxItemRenderer<ISelectedItem>());
		return comboBox;
	}

	private JCheckBox createCheckBox(String text, boolean selected, Color bg) {
		JCheckBox checkBox = new JCheckBox(text, selected);
		checkBox.addActionListener(this::itemStateChanged);
		checkBox.setBackground(bg);
		return checkBox;
	}

	public enum TimePeriod implements ISelectedItem {
		$1DAY("1 day", 1),
		$2DAY("2 day", 2),
		$5DAY("5 day", 5),
		$10DAY("10 day", 10),
		$1Month("1 months", 30),
		$2Month("2 months", 60),
		$3Month("3 months", 90),
		$6Month("6 months", 180),
		$1YEAR("1 year", 360),
		$2YEAR("2 year", 720),
		$3YEAR("3 year", 1800),
		$4YEAR("4 year", 1440),
		$5YEAR("5 year", 1800),
		$10YEAR("10 year", 3600);

		private TimePeriod(String name, int value) {
			this.name = name;
			this.value = value;
		}

		final String name;
		final int value;
		@Override
		public String getName() {
			return name;
		}
		@Override
		public Serializable getValue() {
			return value;
		}
	}

	public enum MovingAverages implements ISelectedItem {
		None("None", "None"), SMA("Simple", "SMA"), EMA("Exponential", "EMA"), TMA("Triangular", "TMA"),
		WMA("Weighted", "WMA");

		private MovingAverages(String name, String value) {
			this.name = name;
			this.value = value;
		}

		final String name;
		final String value;
		@Override
		public String getName() {
			return name;
		}
		@Override
		public Serializable getValue() {
			return value;
		}
		
	}

	public enum TechnicalIndicator implements ISelectedItem {
		None("None", "None"),
		AccDist("Accumulation/Distribution", "AccDist"),
		AroonOsc("Aroon Oscillator", "AroonOsc"),
		Aroon("Aroon Up/Down", "Aroon"),
		ADX("Avg Directional Index", "ADX"),
		ATR("Avg True Range", "ATR"),
		BBW("Bollinger Band Width", "BBW"),
		CMF("Chaikin Money Flow", "CMF"),
		COscillator("Chaikin Oscillator", "COscillator"),
		CVolatility("Chaikin Volatility", "CVolatility"),
		CLV("Close Location Value", "CLV"),
		CCI("Commodity Channel Index", "CCI"),
		DPO("Detrended Price Osc", "DPO"),
		DCW("Donchian Channel Width", "DCW"),
		EMV("Ease of Movement", "EMV"),
		FStoch("Fast Stochastic", "FStoch"),
		MACD("MACD", "MACD"),
		MDX("Mass Index", "MDX"),
		Momentum("Momentum", "Momentum"),
		MFI("Money Flow Index", "MFI"),
		NVI("Neg Volume Index", "NVI"),
		OBV("On Balance Volume", "OBV"),
		Performance("Performance", "Performance"),
		PPO("% Price Oscillator", "PPO"),
		PVO("% Volume Oscillator", "PVO"),
		PVI("Pos Volume Index", "PVI"),
		PVT("Price Volume Trend", "PVT"),
		ROC("Rate of Change", "ROC"),
		RSI("RSI", "RSI"),
		SStoch("Slow Stochastic", "SStoch"),
		StochRSI("StochRSI", "StochRSI"),
		TRIX("TRIX", "TRIX"),
		UO("Ultimate Oscillator", "UO"),
		Vol("Volume", "Vol"),
		WilliamR("William's %R", "WilliamR");

		private TechnicalIndicator(String name, String value) {
			this.name = name;
			this.value = value;
		}

		final String name;
		final String value;
		@Override
		public String getName() {
			return name;
		}
		@Override
		public Serializable getValue() {
			return value;
		}
	}
	
	//
	// Constructor
	//
	public FinanceDemoChart(JFrame app) 
	{
		super(app);
		// Set dialog to modal and non-resizable
		setModal(true);
		setResizable(false);
		setLocation(app.getX(), app.getY());
		setPreferredSize(new Dimension(app.getWidth(), app.getHeight() - 120));
		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		
		// Set title to name of this demo program
		setTitle("ChartDirector Financial Chart Demonstration");

		// Top label bar
		JLabel topLabel = new JLabel("Advanced Software Engineering");
		topLabel.setForeground(new Color(255, 255, 51));
		topLabel.setBackground(new Color(0, 0, 128));
		topLabel.setBorder(new javax.swing.border.EmptyBorder(2, 0, 2, 5));
		topLabel.setHorizontalAlignment(SwingConstants.RIGHT);
		topLabel.setOpaque(true);
		getContentPane().add(topLabel, BorderLayout.NORTH);

		// Left panel
		JPanel leftPanel = new JPanel(null);
		leftPanel.setBackground(new Color(187, 221, 255));

		JLabel lblSymbol=new JLabel("Ticker Symbol");
		leftPanel.add(lblSymbol).setBounds(new Bounds());

		mTickerSymbol = new JTextField("ASE.SYMBOL");
		leftPanel.add(mTickerSymbol).setBounds(new Bounds(lblSymbol));

		JLabel lblPeriod = new JLabel("Time Period");
		leftPanel.add(lblPeriod).setBounds(new Bounds(mTickerSymbol));

		mTimePeriod = createComboBox(TimePeriod.values());
		mTimePeriod.setSelectedIndex(7);
		leftPanel.add(mTimePeriod).setBounds(new Bounds(lblPeriod));

		JLabel lblSize=new JLabel("Chart Size");
		leftPanel.add(lblSize).setBounds(new Bounds(mTimePeriod));

		mChartSize = new JComboBox<>(new String[] { "Small", "Medium", "Large", "Huge" });
		mChartSize.setSelectedIndex(2);
		mChartSize.addActionListener(this::itemStateChanged);
		leftPanel.add(mChartSize).setBounds(new Bounds(lblSize));

		mVolume = createCheckBox("Show Volume Bars", true, leftPanel.getBackground());
		leftPanel.add(mVolume).setBounds(new Bounds(mChartSize));

		mParabolicSAR = createCheckBox("Parabolic SAR", false, leftPanel.getBackground());
		leftPanel.add(mParabolicSAR).setBounds(new Bounds(mVolume));

		mLogScale = createCheckBox("Log Scale", false, leftPanel.getBackground());
		leftPanel.add(mLogScale).setBounds(new Bounds(mParabolicSAR));

		mPercentageScale = createCheckBox("Percentage Scale", false, leftPanel.getBackground());
		leftPanel.add(mPercentageScale).setBounds(new Bounds(mLogScale));

		JLabel lblType=new JLabel("Chart Type");
		leftPanel.add(lblType).setBounds(new Bounds(mPercentageScale));

		mChartType = new JComboBox<>(new String[] {"None","CandleStick","Closing Price","Median Price","OHLC","Typical Price","Weighted Close"});
		mChartType.setSelectedIndex(1);
		mChartType.addActionListener(this::itemStateChanged);
		leftPanel.add(mChartType).setBounds(new Bounds(lblType));

		JLabel lblBand = new JLabel("Price Band");
		leftPanel.add(lblBand).setBounds(new Bounds(mChartType));

		mBand = new JComboBox<>(new String[] {"None","Bollinger Band","Donchian Channel","Envelop (SMA 20 +/- 10%)"});
		mBand.setSelectedIndex(1);
		mBand.addActionListener(this::itemStateChanged);
		leftPanel.add(mBand).setBounds(new Bounds(lblBand));

		JLabel lblMovingAverages = new JLabel("Moving Averages");
		leftPanel.add(lblMovingAverages).setBounds(new Bounds(mBand));

		Box hBox = Box.createHorizontalBox();
		avgType1 = createComboBox(MovingAverages.values());
		avgType1.addActionListener(this::itemStateChanged);
		((JComboBox<?>) hBox.add(avgType1)).setSelectedIndex(1);

		movAvg1 = new JComboBox<>(new Integer[] { 5, 10, 20, 30, 40 });
		movAvg1.addActionListener(this::itemStateChanged);
		((JComboBox<?>)hBox.add(movAvg1)).setSelectedIndex(1);
		leftPanel.add(hBox).setBounds(new Bounds(lblMovingAverages));

		Box hBox2 = Box.createHorizontalBox();
		avgType2 = createComboBox(MovingAverages.values());
		avgType2.addActionListener(this::itemStateChanged);
		((JComboBox<?>) hBox2.add(avgType2)).setSelectedIndex(1);

		movAvg2 = new JComboBox<>(new Integer[] { 5, 10, 25, 35, 45 });
		movAvg2.addActionListener(this::itemStateChanged);
		((JComboBox<?>)hBox2.add(movAvg2)).setSelectedIndex(2);
		final Rectangle bounds = hBox.getBounds();
		bounds.setLocation(hBox.getX(), hBox.getY() + hBox.getHeight() + 2);
		leftPanel.add(hBox2).setBounds(bounds);

		JLabel lblTechnicalIndicators = new JLabel("Technical Indicators");
		leftPanel.add(lblTechnicalIndicators).setBounds(new Bounds(hBox2));

		Indicator1 = createComboBox(TechnicalIndicator.values());
		Indicator2 = createComboBox(TechnicalIndicator.values());
		Indicator3 = createComboBox(TechnicalIndicator.values());
		Indicator4 = createComboBox(TechnicalIndicator.values());

		Indicator1.setSelectedIndex(Indicator1.getItemCount() - 7);
		Indicator2.setSelectedIndex(Indicator1.getItemCount() - 19);
		leftPanel.add(Indicator1).setBounds(new Bounds(lblTechnicalIndicators));
		leftPanel.add(Indicator2).setBounds(new Bounds(Indicator1));
		leftPanel.add(Indicator3).setBounds(new Bounds(Indicator2));
		leftPanel.add(Indicator4).setBounds(new Bounds(Indicator3));

		// Total expected panel size
		leftPanel.setPreferredSize(new Dimension(180, 360));

		// Chart Viewer
		chartViewer1 = new ChartViewer();
		chartViewer1.setBackground(new Color(255, 255, 255));
		chartViewer1.setOpaque(true);
		chartViewer1.setPreferredSize(new Dimension(640, 350));
		chartViewer1.setHorizontalAlignment(SwingConstants.CENTER);
		chartViewer1.addViewPortListener(new ViewPortAdapter() {
			public void viewPortChanged(ViewPortChangedEvent e) {
//				chartViewer1_viewPortChanged(e);
			}
		});	
		chartViewer1.addTrackCursorListener(new TrackCursorAdapter() {
			public void mouseMovedPlotArea(MouseEvent e) {
//				chartViewer1_MouseMovedPlotArea(e);
			}
		});


		// Put the ChartViewer and the scroll bars in the right panel
		JPanel rightPanel = new JPanel(new BorderLayout());
		rightPanel.add(chartViewer1, java.awt.BorderLayout.CENTER);
		
		// Put the leftPanel and rightPanel on the content pane
		getContentPane().add(leftPanel, java.awt.BorderLayout.WEST);
		getContentPane().add(rightPanel, java.awt.BorderLayout.CENTER);
		
		// The chart update timer
//		chartUpdateTimer = new javax.swing.Timer(100, new ActionListener() {
//			public void actionPerformed(ActionEvent evt) {
//				chartUpdateTimer_Tick();
//			}
//		});
		
		// Layout the window
		pack();

		// Initialize the ChartViewer
		initChartViewer(chartViewer1);

		// Output the chart
		drawChart(chartViewer1);
	}
	
	//
	// Initialize the WinChartViewer
	//
	private void initChartViewer(ChartViewer viewer)
	{
		// Enable mouse wheel zooming by setting the zoom ratio to 1.1 per wheel event
		viewer.setMouseWheelZoomRatio(1.1);
	}

	private void itemStateChanged(ActionEvent e) {
		// Output the chart
		if (null != chartViewer1)
			drawChart(chartViewer1);
	}

	protected BaseChart errMsg(String msg)
	{
	    MultiChart m = new MultiChart(400, 200);
	    m.addTitle2(Chart.Center, msg, "Arial", 10).setMaxWidth(m.getWidth());
	    return m;
	}

	/// <summary>
	/// Get the timeStamps, highData, lowData, openData, closeData and volData.
	/// </summary>
	/// <param name="ticker">The ticker symbol for the data series.</param>
	/// <param name="startDate">The starting date/time for the data series.</param>
	/// <param name="endDate">The ending date/time for the data series.</param>
	/// <param name="durationInDays">The number of trading days to get.</param>
	/// <param name="extraPoints">The extra leading data points needed in order to
	/// compute moving averages.</param>
	/// <returns>True if successfully obtain the data, otherwise false.</returns>
	protected boolean getData(String ticker, GregorianCalendar startDate, GregorianCalendar endDate,
	    int durationInDays, int extraPoints)
	{
	    // This method should return false if the ticker symbol is invalid. In this sample code, as we
	    // are using a random number generator for the data, all ticker symbol is allowed, but we still
	    // assumed an empty symbol is invalid.
	    if ("".equals(ticker)) {
	        return false;
	    }

	    // In this demo, we can get 15 min, daily, weekly or monthly data depending on the time range.
	    resolution = 86400;
	    if (durationInDays <= 10) {
	        // 10 days or less, we assume 15 minute data points are available
	        resolution = 900;

	        // We need to adjust the startDate backwards for the extraPoints. We assume 6.5 hours
	        // trading time per day, and 5 trading days per week.
	        double dataPointsPerDay = 6.5 * 3600 / resolution;
	        GregorianCalendar adjustedStartDate = new GregorianCalendar(startDate.get(Calendar.YEAR),
	            startDate.get(Calendar.MONTH), startDate.get(Calendar.DAY_OF_MONTH));
	        adjustedStartDate.add(Calendar.DAY_OF_MONTH,
	            -(int)Math.ceil(extraPoints / dataPointsPerDay * 7 / 5) - 2);

	        // Get the required 15 min data
	        get15MinData(ticker, adjustedStartDate.getTime(), endDate.getTime());

	    } else if (durationInDays >= 4.5 * 360) {
	        // 4 years or more - use monthly data points.
	        resolution = 30 * 86400;

	        // Adjust startDate backwards to cater for extraPoints
	        GregorianCalendar adjustedStartDate = (GregorianCalendar)startDate.clone();
	        adjustedStartDate.add(Calendar.MONTH, -extraPoints);

	        // Get the required monthly data
	        getMonthlyData(ticker, adjustedStartDate.getTime(), endDate.getTime());

	    } else if (durationInDays >= 1.5 * 360) {
	        // 1 year or more - use weekly points.
	        resolution = 7 * 86400;

	        // Adjust startDate backwards to cater for extraPoints
	        GregorianCalendar adjustedStartDate = (GregorianCalendar)startDate.clone();
	        adjustedStartDate.add(Calendar.DAY_OF_MONTH, -extraPoints * 7 - 6);

	        // Get the required weekly data
	        getWeeklyData(ticker, adjustedStartDate.getTime(), endDate.getTime());

	    } else {
	        // Default - use daily points
	        resolution = 86400;

	        // Adjust startDate backwards to cater for extraPoints. We multiply the days by 7/5 as we
	        // assume 1 week has 5 trading days.
	        GregorianCalendar adjustedStartDate = new GregorianCalendar(startDate.get(Calendar.YEAR),
	            startDate.get(Calendar.MONTH), startDate.get(Calendar.DAY_OF_MONTH));
	        adjustedStartDate.add(Calendar.DAY_OF_MONTH, - (extraPoints * 7 + 4) / 5 - 2);

	        // Get the required daily data
	        getDailyData(ticker, adjustedStartDate.getTime(), endDate.getTime());
	    }

	    return true;
	}
	
	/// <summary>
	/// Get 15 minutes data series for timeStamps, highData, lowData, openData, closeData
	/// and volData.
	/// </summary>
	/// <param name="ticker">The ticker symbol for the data series.</param>
	/// <param name="startDate">The starting date/time for the data series.</param>
	/// <param name="endDate">The ending date/time for the data series.</param>
	protected void get15MinData(String ticker, Date startDate, Date endDate)
	{
	    //
	    // In this demo, we use a random number generator to generate the data. In practice, you may get
	    // the data from a database or by other means. If you do not have 15 minute data, you may modify
	    // the "drawChart" method below to not using 15 minute data.
	    //
	    generateRandomData(ticker, startDate, endDate, 900);
	}

	/// <summary>
	/// Get daily data series for timeStamps, highData, lowData, openData, closeData
	/// and volData.
	/// </summary>
	/// <param name="ticker">The ticker symbol for the data series.</param>
	/// <param name="startDate">The starting date/time for the data series.</param>
	/// <param name="endDate">The ending date/time for the data series.</param>
	protected void getDailyData(String ticker, Date startDate, Date endDate)
	{
	    //
	    // In this demo, we use a random number generator to generate the data. In practice, you may get
	    // the data from a database or by other means.
	    //
	    generateRandomData(ticker, startDate, endDate, 86400);
	}
	
	/// <summary>
	/// Get weekly data series for timeStamps, highData, lowData, openData, closeData
	/// and volData.
	/// </summary>
	/// <param name="ticker">The ticker symbol for the data series.</param>
	/// <param name="startDate">The starting date/time for the data series.</param>
	/// <param name="endDate">The ending date/time for the data series.</param>
	protected void getWeeklyData(String ticker, Date startDate, Date endDate)
	{
	    //
	    // If you do not have weekly data, you may call "getDailyData(startDate, endDate)" to get daily
	    // data, then call "convertDailyToWeeklyData()" to convert to weekly data.
	    //
	    generateRandomData(ticker, startDate, endDate, 86400 * 7);
	}

	/// <summary>
	/// Get monthly data series for timeStamps, highData, lowData, openData, closeData
	/// and volData.
	/// </summary>
	/// <param name="ticker">The ticker symbol for the data series.</param>
	/// <param name="startDate">The starting date/time for the data series.</param>
	/// <param name="endDate">The ending date/time for the data series.</param>
	protected void getMonthlyData(String ticker, Date startDate, Date endDate)
	{
	    //
	    // If you do not have weekly data, you may call "getDailyData(startDate, endDate)" to get daily
	    // data, then call "convertDailyToMonthlyData()" to convert to monthly data.
	    //
	    generateRandomData(ticker, startDate, endDate, 86400 * 30);
	}

	/// <summary>
	/// A random number generator designed to generate realistic financial data.
	/// </summary>
	/// <param name="ticker">The ticker symbol for the data series.</param>
	/// <param name="startDate">The starting date/time for the data series.</param>
	/// <param name="endDate">The ending date/time for the data series.</param>
	/// <param name="resolution">The period of the data series.</param>
	protected void generateRandomData(String ticker, Date startDate, Date endDate, int resolution)
	{
	    FinanceSimulator db = new FinanceSimulator(ticker, startDate, endDate, resolution);
	    timeStamps = db.getTimeStamps();
	    highData = db.getHighData();
	    lowData = db.getLowData();
	    openData = db.getOpenData();
	    closeData = db.getCloseData();
	    volData = db.getVolData();
	}
	
	/// <summary>
	/// A utility to convert daily to weekly data.
	/// </summary>
	protected void convertDailyToWeeklyData()
	{
	    aggregateData(new ArrayMath(timeStamps).selectStartOfWeek());
	}

	/// <summary>
	/// A utility to convert daily to monthly data.
	/// </summary>
	protected void convertDailyToMonthlyData()
	{
	    aggregateData(new ArrayMath(timeStamps).selectStartOfMonth());
	}

	/// <summary>
	/// An internal method used to aggregate daily data.
	/// </summary>
	protected void aggregateData(ArrayMath aggregator)
	{
	    timeStamps = Chart.NTime(aggregator.aggregate(Chart.CTime(timeStamps), Chart.AggregateFirst));
	    highData = aggregator.aggregate(highData, Chart.AggregateMax);
	    lowData = aggregator.aggregate(lowData, Chart.AggregateMin);
	    openData = aggregator.aggregate(openData, Chart.AggregateFirst);
	    closeData = aggregator.aggregate(closeData, Chart.AggregateLast);
	    volData = aggregator.aggregate(volData, Chart.AggregateSum);
	}

	/// <summary>
	/// Create a financial chart according to user selections. The user selections are
	/// encoded in the query parameters.
	/// </summary>
	public void drawChart(ChartViewer viewer)
	{
	    // In this demo, we just assume we plot up to the latest time. So end date is now.
	    GregorianCalendar endDate = new GregorianCalendar();

	    // If the trading day has not yet started (before 9:30am), or if the end date is on on Sat or
	    // Sun, we set the end date to 4:00pm of the last trading day
	    while ((endDate.get(Calendar.HOUR_OF_DAY) * 60 + endDate.get(Calendar.MINUTE) <
	        9 * 60 + 30) || (endDate.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) ||
	        (endDate.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY))
	    {
	        endDate.add(Calendar.DAY_OF_MONTH, -1);
	        endDate.set(Calendar.HOUR_OF_DAY, 16);
	        endDate.set(Calendar.MINUTE, 0);
	        endDate.set(Calendar.SECOND, 0);
	    }

	    // The duration selected by the user
	    TimePeriod durationInDays = (TimePeriod)mTimePeriod.getSelectedItem();

	    // Compute the start date by subtracting the duration from the end date.
	    GregorianCalendar startDate;
	    if (durationInDays.value >= 30) {
	        // More or equal to 30 days - so we use months as the unit
	        startDate = new GregorianCalendar(endDate.get(Calendar.YEAR),
	            endDate.get(Calendar.MONTH), 1);
	        startDate.add(Calendar.MONTH, -durationInDays.value / 30);
	    } else {
	        // Less than 30 days - use day as the unit. The starting point of the axis is always at the
	        // start of the day (9:30am). Note that we use trading days, so we skip Sat and Sun in
	        // counting the days.
	        startDate = new GregorianCalendar(endDate.get(Calendar.YEAR),
	            endDate.get(Calendar.MONTH), endDate.get(Calendar.DAY_OF_MONTH), 9, 30, 0);
	        for (int i = 1; i < durationInDays.value; ++i)
	        {
	            startDate.add(Calendar.DAY_OF_MONTH,
	                (startDate.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY) ? -3 : -1);
	        }
	    }

	    // The moving average periods selected by the user.
	    int avgPeriod1 = 0;
	    try { avgPeriod1 = (Integer)movAvg1.getSelectedItem(); }
	    catch (Exception e) { avgPeriod1 = 0; }
	    int avgPeriod2 = 0;
	    try { avgPeriod2 = (Integer)movAvg2.getSelectedItem(); }
	    catch (Exception e) { avgPeriod2 = 0; }

	    if (avgPeriod1 < 0) {
	        avgPeriod1 = 0;
	    } else if (avgPeriod1 > 300) {
	        avgPeriod1 = 300;
	    }

	    if (avgPeriod2 < 0) {
	        avgPeriod2 = 0;
	    } else if (avgPeriod2 > 300) {
	        avgPeriod2 = 300;
	    }

	    // We need extra leading data points in order to compute moving averages.
	    int extraPoints = 20;
	    if (avgPeriod1 > extraPoints) {
	        extraPoints = avgPeriod1;
	    }
	    if (avgPeriod2 > extraPoints) {
	        extraPoints = avgPeriod2;
	    }

	    // Get the data series to compare with, if any.
	    String compareKey = "";
	    compareData = null;
	    if (getData(compareKey, startDate, endDate, durationInDays.value, extraPoints)) {
	          compareData = closeData;
	    }

	    // The data series we want to get.
		String tickerKey = mTickerSymbol.getText();
	    if (!getData(tickerKey, startDate, endDate, durationInDays.value, extraPoints)) {
	    	viewer.setChart(errMsg("Please enter a valid ticker symbol"));
	        return;
	    }

	    // We now confirm the actual number of extra points (data points that are before the start date)
	    // as inferred using actual data from the database.
	    extraPoints = timeStamps.length;
	    Date cutOff = startDate.getTime();
	    for (int i = 0; i < timeStamps.length; ++i)
	    {
	        if (!timeStamps[i].before(cutOff))
	        {
	            extraPoints = i;
	            break;
	        }
	    }

	    // Check if there is any valid data
	    if (extraPoints >= timeStamps.length) {
	        // No data - just display the no data message.
	    	viewer.setChart(errMsg("No data available for the specified time period"));
	    	return;
	    }

	    // In some finance chart presentation style, even if the data for the latest day is not fully
	    // available, the axis for the entire day will still be drawn, where no data will appear near
	    // the end of the axis.
	    if (resolution < 86400) {
	        // Add extra points to the axis until it reaches the end of the day. The end of day is
	        // assumed to be 16:00 (it depends on the stock exchange).
	        GregorianCalendar lastTime = new GregorianCalendar();
	        lastTime.setTime(timeStamps[timeStamps.length - 1]);
	        int extraTrailingPoints = (int)((16 * 3600 - lastTime.get(Calendar.HOUR_OF_DAY) * 3600
	            - lastTime.get(Calendar.MINUTE) * 60 - lastTime.get(Calendar.SECOND)) / resolution);
	        if (extraTrailingPoints > 0)
	        {
	            Date[] extendedTimeStamps = new Date[timeStamps.length + extraTrailingPoints];
	            System.arraycopy(timeStamps, 0, extendedTimeStamps, 0, timeStamps.length);
	            for (int i = 0; i < extraTrailingPoints; ++i)
	            {
	                lastTime.add(Calendar.SECOND, resolution);
	                extendedTimeStamps[i + timeStamps.length] = (Date)lastTime.getTime().clone();
	            }
	            timeStamps = extendedTimeStamps;
	        }
	    }

	    //
	    // At this stage, all data are available. We can draw the chart as according to user input.
	    //

	    //
	    // Determine the chart size. In this demo, user can select 4 different chart sizes. Default is
	    // the large chart size.
	    //
	    int width = 780;
	    int mainHeight = 255;
	    int indicatorHeight = 80;

		String size = (String) mChartSize.getSelectedItem();
	    if ("Small".equals(size)) {
	        // Small chart size
	        width = 450;
	        mainHeight = 160;
	        indicatorHeight = 60;
	    } else if ("Medium".equals(size)) {
	        // Medium chart size
	        width = 620;
	        mainHeight = 215;
	        indicatorHeight = 70;
	    } else if ("Huge".equals(size)) {
	        // Huge chart size
	        width = 1000;
	        mainHeight = 320;
	        indicatorHeight = 90;
	    }

	    // Create the chart object using the selected size
		FinanceChart m = new FinanceChart(width);

		// Add a title to the chart
		m.addPlotAreaTitle(Chart.TopCenter,"ChartDirector Financial Chart Demonstration");


	    // Set the data into the chart object
	    m.setData(timeStamps, highData, lowData, openData, closeData, volData, extraPoints);

	    //
	    // We configure the title of the chart. In this demo chart design, we put the company name as
	    // the top line of the title with left alignment.
	    //
	    m.addPlotAreaTitle(Chart.TopLeft, tickerKey);

	    // We displays the current date as well as the data resolution on the next line.
	    String resolutionText = "";
	    if (resolution == 30 * 86400) {
	        resolutionText = "Monthly";
	    } else if (resolution == 7 * 86400) {
	        resolutionText = "Weekly";
	    } else if (resolution == 86400) {
	        resolutionText = "Daily";
	    } else if (resolution == 900) {
	        resolutionText = "15-min";
	    }

	    m.addPlotAreaTitle(Chart.BottomLeft, "<*font=Arial,size=8*>" + m.formatValue(new Date(),
	        "mmm dd, yyyy") + " - " + resolutionText + " chart");

	    // A copyright message at the bottom left corner the title area
	    m.addPlotAreaTitle(Chart.BottomRight, "<*font=Arial,size=8*>(c) Advanced Software Engineering");

	    //
	    // Add the first techical indicator according. In this demo, we draw the first indicator on top
	    // of the main chart.
	    //
		addIndicator(m, ((TechnicalIndicator) Indicator1.getSelectedItem()).value, indicatorHeight);

	    //
	    // Add the main chart
	    //
	    m.addMainChart(mainHeight);

	    //
	    // Set log or linear scale according to user preference
	    //
	    if (mLogScale.isSelected()) {
	        m.setLogScale(true);
	    }

	    //
	    // Set axis labels to show data values or percentage change to user preference
	    //
	    if (mPercentageScale.isSelected()) {
	        m.setPercentageAxis();
	    }

	    //
	    // Draw any price line the user has selected
	    //
	    String mainType = (String) mChartType.getSelectedItem();
	    if ("Closing Price".equals(mainType)) {
	        m.addCloseLine(0x000040);
	    } else if ("Typical Price".equals(mainType)) {
	        m.addTypicalPrice(0x000040);
	    } else if ("Weighted Close".equals(mainType)) {
	        m.addWeightedClose(0x000040);
	    } else if ("Median Price".equals(mainType)) {
	        m.addMedianPrice(0x000040);
	    }

	    //
	    // Add comparison line if there is data for comparison
	    //
	    if (compareData != null) {
	        if (compareData.length > extraPoints) {
	            m.addComparison(compareData, 0x0000ff, compareKey);
	        }
	    }

	    //
	    // Add moving average lines.
	    //
		addMovingAvg(m, ((MovingAverages) avgType1.getSelectedItem()).value, avgPeriod1, 0x663300);
		addMovingAvg(m, ((MovingAverages) avgType2.getSelectedItem()).value, avgPeriod2, 0x9900ff);

	    //
	    // Draw candlesticks or OHLC symbols if the user has selected them.
	    //
	    if ("CandleStick".equals(mainType)) {
	        m.addCandleStick(0xff3333, 0x33ff33);
	    } else if ("OHLC".equals(mainType)) {
	        m.addHLOC(0xcc0000, 0x008800);
	    }

	    //
	    // Add parabolic SAR if necessary
	    //
	    if (mParabolicSAR.isSelected()) {
	        m.addParabolicSAR(0.02, 0.02, 0.2, Chart.DiamondShape, 5, 0x008800, 0x000000);
	    }

	    //
	    // Add price band/channel/envelop to the chart according to user selection
	    //
		String bandType = (String) mBand.getSelectedItem();
	    if ("Bollinger Band".equals(bandType)) {
	        m.addBollingerBand(20, 2, 0x9999ff, 0xc06666ff);
	    } else if ("Donchian Channel".equals(bandType)) {
	        m.addDonchianChannel(20, 0x9999ff, 0xc06666ff);
	    } else if ("Envelop (SMA 20 +/- 10%)".equals(bandType)) {
	        m.addEnvelop(20, 0.1, 0x9999ff, 0xc06666ff);
	    }

	    //
	    // Add volume bars to the main chart if necessary
	    //
	    if (mVolume.isSelected()) {
	        m.addVolBars(indicatorHeight, 0xff9999, 0x99ff99, 0xc0c0c0);
	    }

	    //
	    // Add additional indicators as according to user selection.
	    //
		addIndicator(m, ((TechnicalIndicator) Indicator2.getSelectedItem()).value, indicatorHeight);
		addIndicator(m, ((TechnicalIndicator) Indicator3.getSelectedItem()).value, indicatorHeight);
		addIndicator(m, ((TechnicalIndicator) Indicator4.getSelectedItem()).value, indicatorHeight);
	    viewer.setChart(m);
	}

	/// <summary>
	/// Add a moving average line to the FinanceChart object.
	/// </summary>
	/// <param name="m">The FinanceChart object to add the line to.</param>
	/// <param name="avgType">The moving average type (SMA/EMA/TMA/WMA).</param>
	/// <param name="avgPeriod">The moving average period.</param>
	/// <param name="color">The color of the line.</param>
	/// <returns>The LineLayer object representing line layer created.</returns>
	protected LineLayer addMovingAvg(FinanceChart m, String avgType, int avgPeriod, int color)
	{
	    if (avgPeriod > 1) {
	        if ("SMA".equals(avgType)) {
	            return m.addSimpleMovingAvg(avgPeriod, color);
	        } else if ("EMA".equals(avgType)) {
	            return m.addExpMovingAvg(avgPeriod, color);
	        } else if ("TMA".equals(avgType)) {
	            return m.addTriMovingAvg(avgPeriod, color);
	        } else if ("WMA".equals(avgType)) {
	            return m.addWeightedMovingAvg(avgPeriod, color);
	        }
	    }
	    return null;
	}

	/// <summary>
	/// Add an indicator chart to the FinanceChart object. In this demo example, the
	/// indicator parameters (such as the period used to compute RSI, colors of the lines,
	/// etc.) are hard coded to commonly used values. You are welcome to design a more
	/// complex user interface to allow users to set the parameters.
	/// </summary>
	/// <param name="m">The FinanceChart object to add the line to.</param>
	/// <param name="indicator">The selected indicator.</param>
	/// <param name="height">Height of the chart in pixels</param>
	/// <returns>The XYChart object representing indicator chart.</returns>
	protected XYChart addIndicator(FinanceChart m, String indicator, int height)
	{
	    if ("RSI".equals(indicator)) {
	        return m.addRSI(height, 14, 0x800080, 20, 0xff6666, 0x6666ff);
	    } else if ("StochRSI".equals(indicator)) {
	        return m.addStochRSI(height, 14, 0x800080, 30, 0xff6666, 0x6666ff);
	    } else if ("MACD".equals(indicator)) {
	        return m.addMACD(height, 26, 12, 9, 0x0000ff, 0xff00ff, 0x008000);
	    } else if ("FStoch".equals(indicator)) {
	        return m.addFastStochastic(height, 14, 3, 0x006060, 0x606000);
	    } else if ("SStoch".equals(indicator)) {
	        return m.addSlowStochastic(height, 14, 3, 0x006060, 0x606000);
	    } else if ("ATR".equals(indicator)) {
	        return m.addATR(height, 14, 0x808080, 0x0000ff);
	    } else if ("ADX".equals(indicator)) {
	        return m.addADX(height, 14, 0x008000, 0x800000, 0x000080);
	    } else if ("DCW".equals(indicator)) {
	        return m.addDonchianWidth(height, 20, 0x0000ff);
	    } else if ("BBW".equals(indicator)) {
	        return m.addBollingerWidth(height, 20, 2, 0x0000ff);
	    } else if ("DPO".equals(indicator)) {
	        return m.addDPO(height, 20, 0x0000ff);
	    } else if ("PVT".equals(indicator)) {
	        return m.addPVT(height, 0x0000ff);
	    } else if ("Momentum".equals(indicator)) {
	        return m.addMomentum(height, 12, 0x0000ff);
	    } else if ("Performance".equals(indicator)) {
	        return m.addPerformance(height, 0x0000ff);
	    } else if ("ROC".equals(indicator)) {
	        return m.addROC(height, 12, 0x0000ff);
	    } else if ("OBV".equals(indicator)) {
	        return m.addOBV(height, 0x0000ff);
	    } else if ("AccDist".equals(indicator)) {
	        return m.addAccDist(height, 0x0000ff);
	    } else if ("CLV".equals(indicator)) {
	        return m.addCLV(height, 0x0000ff);
	    } else if ("WilliamR".equals(indicator)) {
	        return m.addWilliamR(height, 14, 0x800080, 30, 0xff6666, 0x6666ff);
	    } else if ("Aroon".equals(indicator)) {
	        return m.addAroon(height, 14, 0x339933, 0x333399);
	    } else if ("AroonOsc".equals(indicator)) {
	        return m.addAroonOsc(height, 14, 0x0000ff);
	    } else if ("CCI".equals(indicator)) {
	        return m.addCCI(height, 20, 0x800080, 100, 0xff6666, 0x6666ff);
	    } else if ("EMV".equals(indicator)) {
	        return m.addEaseOfMovement(height, 9, 0x006060, 0x606000);
	    } else if ("MDX".equals(indicator)) {
	        return m.addMassIndex(height, 0x800080, 0xff6666, 0x6666ff);
	    } else if ("CVolatility".equals(indicator)) {
	        return m.addChaikinVolatility(height, 10, 10, 0x0000ff);
	    } else if ("COscillator".equals(indicator)) {
	        return m.addChaikinOscillator(height, 0x0000ff);
	    } else if ("CMF".equals(indicator)) {
	        return m.addChaikinMoneyFlow(height, 21, 0x008000);
	    } else if ("NVI".equals(indicator)) {
	        return m.addNVI(height, 255, 0x0000ff, 0x883333);
	    } else if ("PVI".equals(indicator)) {
	        return m.addPVI(height, 255, 0x0000ff, 0x883333);
	    } else if ("MFI".equals(indicator)) {
	        return m.addMFI(height, 14, 0x800080, 30, 0xff6666, 0x6666ff);
	    } else if ("PVO".equals(indicator)) {
	        return m.addPVO(height, 26, 12, 9, 0x0000ff, 0xff00ff, 0x008000);
	    } else if ("PPO".equals(indicator)) {
	        return m.addPPO(height, 26, 12, 9, 0x0000ff, 0xff00ff, 0x008000);
	    } else if ("UO".equals(indicator)) {
	        return m.addUltimateOscillator(height, 7, 14, 28, 0x800080, 20, 0xff6666, 0x6666ff);
	    } else if ("Vol".equals(indicator)) {
	        return m.addVolIndicator(height, 0x99ff99, 0xff9999, 0xc0c0c0);
	    } else if ("TRIX".equals(indicator)) {
	        return m.addTRIX(height, 12, 0x0000ff);
	    }
	    return null;
	}
}
